#include "nosal/log/logger.h"
#include "nosal/log/log_stream.h"
//#include "nosal/log/internal/utility.h"
#include "slog2/slog2_context.h"
#include "slog2/slog2_context_data.h"
#include <algorithm>
#include <iostream>

namespace netaos {
namespace nosal {
namespace log {

namespace
{
Slog2Context* toSlog2Context(void* context) noexcept
{
    return static_cast<Slog2Context*>(context);
}

Slog2ContextData* toSlog2ContextData(void* context_data) noexcept
{
    return static_cast<Slog2ContextData*>(context_data);
}

Slog2SeverityLevel toSlog2SeverityLevel(LogLevel loglevel)
{
    Slog2SeverityLevel severity_level = Slog2SeverityLevel::kShutdown;
    switch (loglevel)
    {
        case LogLevel::kFatal:
            severity_level = Slog2SeverityLevel::kCritical;
            break;
        case LogLevel::kError:
            severity_level = Slog2SeverityLevel::kError;
            break;
        case LogLevel::kWarn:
            severity_level = Slog2SeverityLevel::kWarning;
            break;
        case LogLevel::kInfo:
            severity_level = Slog2SeverityLevel::kInfo;//kNotice
            break;
        case LogLevel::kDebug:
            severity_level = Slog2SeverityLevel::kDebug1;
            break;
        case LogLevel::kVerbose:
            severity_level = Slog2SeverityLevel::kDebug2;
           break;
        default:
            severity_level = Slog2SeverityLevel::kShutdown;
            break;
    }
    return severity_level;
}


LogLevel toLogLevel(Slog2SeverityLevel severity_level)
{
    LogLevel loglevel = LogLevel::kOff;
    switch (severity_level)
    {
        case Slog2SeverityLevel::kCritical:
            loglevel = LogLevel::kFatal;
            break;
        case Slog2SeverityLevel::kError:
            loglevel = LogLevel::kError;
            break;
        case Slog2SeverityLevel::kWarning:
            loglevel = LogLevel::kWarn;
            break;
        case Slog2SeverityLevel::kInfo://kNotice
            loglevel = LogLevel::kInfo;
            break;
        case Slog2SeverityLevel::kDebug1:
            loglevel = LogLevel::kDebug;
            break;
        case Slog2SeverityLevel::kDebug2:
            loglevel = LogLevel::kVerbose;
            break;
        default:
            loglevel = LogLevel::kOff;
            break;
    }

    return loglevel;
}

}  // namespace

LogStream::LogStream(LogLevel loglevel, const Logger& logger) noexcept
{
    logdata_ = static_cast<void*>(new Slog2ContextData);
    (void)toSlog2ContextData(logdata_)->Initialize(
		    toSlog2Context(logger.getContext()));

    if (logger.IsEnabled(loglevel)) {
	logret_ = static_cast<internal::LogReturnValue>(
			toSlog2ContextData(logdata_)->WriteStart(
			       	toSlog2SeverityLevel(loglevel)));
    } else {
        logret_ = internal::LogReturnValue::kReturnLoggingDisabled;
    }
}

LogStream::~LogStream()
{
    if (isInitialized()) {
        (void)toSlog2ContextData(logdata_)->WriteFinish();
    }

    delete toSlog2ContextData(logdata_);
}

void LogStream::Flush() noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->WriteFinish();
	logret_ = static_cast<internal::LogReturnValue>(toSlog2ContextData(logdata_)->WriteStart());
    }
}

LogStream& LogStream::operator<<(bool value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(uint8_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(uint16_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(uint32_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(uint64_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(int8_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(int16_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(int32_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(int64_t value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(float value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(double value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(const char* value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream& LogStream::operator<<(const std::string value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream &LogStream::operator<<(const std::vector<uint8_t> value) noexcept
{
    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(value);
    }
    return *this;
}

LogStream &LogStream::WithLocation(std::string file, int line) noexcept
{
    if (isInitialized()) {
        const std::string separator = ":";
        std::string line_string = std::to_string(line);
        std::string location_string = file + separator + line_string;
	toSlog2ContextData(logdata_)->Write(location_string);
    }
    return *this;
}

LogStream& LogStream::operator<<(LogLevel value) noexcept
{
    std::string loglevel_string{};
    /*
    const std::string loglevel_off_string = "Off";
    const std::string loglevel_fatal_string = "Fatal";
    const std::string loglevel_error_string = "Error";
    const std::string loglevel_warning_string = "Warning";
    const std::string loglevel_info_string = "Info";
    const std::string loglevel_debug_string = "Debug";
    const std::string loglevel_verbose_string = "Verbose";
    */
    switch (value)
    {
    case LogLevel::kOff:
        loglevel_string += "Off";
        break;
    case LogLevel::kFatal:
        loglevel_string += "Fatal";
        break;
    case LogLevel::kError:
        loglevel_string += "Error";
        break;
    case LogLevel::kWarn:
        loglevel_string += "Warning";
        break;
    case LogLevel::kInfo:
        loglevel_string += "Info";
        break;
    case LogLevel::kDebug:
        loglevel_string += "Debug";
        break;
    case LogLevel::kVerbose:
        loglevel_string += "Verbose";
        break;
    }

    if (isInitialized()) {
	toSlog2ContextData(logdata_)->Write(loglevel_string);
    }
    return *this;
}


#if 0
LogStream& operator<<(LogStream& out, const nosal::core::ErrorCode& value) noexcept
{
    if (out.isInitialized()) {
        constexpr size_t BUFSIZE = 1024;
        char buf[BUFSIZE];
        int const len = snprintf(buf, BUFSIZE, "%s:%d:", value.Domain().Name(), value.Value());
        if (len > 0 && static_cast<std::size_t>(len) < BUFSIZE) {
            size_t const maxMessageLen = std::min(value.Message().size(), BUFSIZE - len);
            snprintf(buf + len, BUFSIZE - len, "%.*s", static_cast<int>(maxMessageLen), value.Message().data());

            (void)slog2_write_utf8_string(toContextData(out.logLocalData_), buf);
        }
    }

    return out;
}
LogStream& LogStream::operator<<(nosal::core::Span<const nosal::core::Byte> value) noexcept
{
    if (isInitialized()) {
        //(void)slog2_write_raw(
            toContextData(logLocalData_), static_cast<void*>(const_cast<nosal::core::Byte*>(value.data())), value.size());
    }
    return *this;
}
LogStream& LogStream::WithLocation(nosal::core::StringView file, int line) noexcept
{
    if (isInitialized() && getBufSize() == 0) {
        //gcy (void)slog2_write_sized_utf8_string(toContextData(logLocalData_), file.data(), file.size());
        //(void)slog2_write_int32(toContextData(logLocalData_), line);
    }
    return *this;
}

/* Support for extra types that transforms into native types */
LogStream& operator<<(LogStream& out, LogLevel value) noexcept
{
    return (out << internal::to_string(value));
}
LogStream& operator<<(LogStream& out, const nosal::core::InstanceSpecifier& value) noexcept
{
    out << value.ToString().data();
    return out;
}
#endif

void LogStream::putValue(bool value, const std::string& name, const std::string& /*ignore*/, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator = ":";
        const std::string true_string = "True";
        const std::string false_string = "False";
        std::string value_string = value ? true_string : false_string;
        std::string data_string = name + separator + value_string;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(uint8_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string unit_string = std::string(unit);
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(uint16_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(uint32_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(uint64_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(int8_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(int16_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(int32_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(int64_t value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(float value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(double value, const std::string& name, const std::string& unit, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator1 = ":";
        const std::string separator2 = " ";
        std::string value_string = std::to_string(value);
        std::string data_string = name + separator1 + value_string + separator2 + unit;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(const std::string& value, const std::string& name, const std::string& /*ignore*/, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator = ":";
        std::string value_string = std::string(value);
        std::string data_string = name + separator + value_string;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

void LogStream::putValue(const char* value, const std::string& name, const std::string& /*ignore*/, Format const& fmt) noexcept
{
    static_cast<void>(fmt);
    if (isInitialized()) {
        const std::string separator = ":";
        const std::string separator2 = " ";
        std::string value_string = std::string(value);
        std::string data_string = name + separator + value_string;
        toSlog2ContextData(logdata_)->Write(data_string);
    }
}

bool LogStream::isInitialized() const noexcept
{
    return logret_ >= internal::LogReturnValue::kReturnOk;
}

}  // namespace log
}  // namespace nosal
}  // namespace netaos
